package com.transfiguro.converters;

import ch.qos.logback.core.StopWatch;
import com.transfiguro.datamodel.Statement;
import com.transfiguro.datamodel.csmodels.*;
import com.transfiguro.datamodel.javamodels.*;
import com.transfiguro.exceptions.ConversionFailedException;
import com.transfiguro.utilities.FileUtilities;
import com.transfiguro.utilities.MappingXMLParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.InputStream;

/**
 * This class is an implementation of {@link StatementDispatcher}. It provides methods
 * to convert different implementations of {@link Statement}
 */
public class StatementConverter implements StatementDispatcher {
    private final Logger logger = LoggerFactory.getLogger(StatementConverter.class);
    private VariableConverter variableConverter;
    private static File methodXMLMapping;
    private static MappingXMLParser xmlParser;
    private static StatementConverter statementConverter = null;

    protected StatementConverter() {

    }

    public static StatementConverter getStatementConverter() {
        if (statementConverter == null) {
            statementConverter = new StatementConverter();
        }
        return statementConverter;
    }

    /**
     * Initializer method
     */
    public synchronized void init() {
        final ClassPathResource resource = new ClassPathResource("mappingxmls/LogicMapping.xml");
        final InputStream stream = ClassLoader.getSystemResourceAsStream("mappingxmls/LogicMapping.xml");
        final File tempFile = new File(".", "LogicMapping.xml");
        FileUtilities.writeTempFile(stream, tempFile);
        methodXMLMapping = tempFile;
        xmlParser = new MappingXMLParser(methodXMLMapping);
    }

    /**
     * This method checks the type of <code>statement</code> provided and
     * dispatches it to the right methods
     *
     * @param statement The <code>statement</code> to be converted
     * @return An instance of the converted <code>statement</code>
     */
    public Statement convert(final Statement statement) {
        init();

        Statement result = null;
        if (statement instanceof ForEachStatement) {
            result = convert((ForEachStatement) statement);
        }

        if (statement instanceof NCBlock) {
            result = convert((NCBlock) statement);
        }

        if (statement instanceof MethodCallStatement) {
            result = convert((MethodCallStatement) statement);
        }

        if (statement instanceof ForStatement) {
            result = convert((ForStatement) statement);
        }

        if (statement instanceof BlockStatement) {
            result = convert((BlockStatement) statement);
        }

        return result;
    }

    /**
     * This method converts a java {@link ForEachStatement} to C# {@link CSForEachStatement}
     *
     * @param model The <code>ForEachStatement</code> model to be converted
     * @return An instance of <code>CSForEachStatement</code> created by
     *         converting the java ForEachStatement
     */

    public CSForEachStatement convert(final ForEachStatement model) {
        CSForEachStatement result = new CSForEachStatement();
        logger.info("ForEach Conversion");
        if (model.getStatements() != null) {
            for (final Statement internalStatement : model.getStatements()) {
                result.addStatement(convert(internalStatement));
            }
        }
        final StopWatch stopWatch = new StopWatch("StatementConversionTimer");
        try {
            variableConverter = new VariableConverter(model.getIterator());
            variableConverter.convert();
            result.setIterator(variableConverter.getCsVariable());

            //TODO Optimize code. Multiple instances being used
            variableConverter = new VariableConverter(model.getVariable());
            variableConverter.convert();
            result.setVariable(variableConverter.getCsVariable());

            result.setLoopBody(model.getLoopBody());
        } catch (final ConversionFailedException e) {
            logger.debug("Exception: {}", e.getMessage());
        }
        stopWatch.stop();
        result.setConversionTime(stopWatch.getResultInSeconds());
        return result;
    }

    /**
     * This method converts a java {@link NCBlock} to C# {@link CSNCBlock}
     *
     * @param model The <code>NCBlock</code> model to be converted
     * @return An instance of <code>CSNCBlock</code> created by
     *         converting the given <code>NCBlock</code>
     */
    public CSNCBlock convert(final NCBlock model) {
        logger.info("NCBlock Conversion");
        CSNCBlock result = new CSNCBlock();
        if (model.getStatements() != null) {
            for (final Statement internalStatement : model.getStatements()) {
                result.addStatement(convert(internalStatement));
            }
        }
        final StopWatch stopWatch = new StopWatch("StatementConversionTimer");
        result.setStatement(model.getStatement());
        stopWatch.stop();
        result.setConversionTime(stopWatch.getResultInSeconds());
        return result;
    }

    /**
     * This method converts a java {@link MethodCallStatement} to C# {@link CSGenericMethodCall}
     *
     * @param model The <code>MethodCallStatement</code> model to be converted
     * @return An instance of <code>CSGenericMethodCall</code> created by converting the given
     *         <code>MethodCallStatement</code>
     */
    public CSGenericMethodCall convert(final MethodCallStatement model) {
        logger.info("MethodCall Conversion");
        CSGenericMethodCall result = new CSGenericMethodCall();
        if (model.getStatements() != null) {
            for (final Statement internalStatement : model.getStatements()) {
                result.addStatement(convert(internalStatement));
            }
        }
        final StopWatch stopWatch = new StopWatch("StatementConversionTimer");
        //Not converting args now
        //TODO Look for a better way of handling this
        result.setArgs(model.getArgs());

        if (model.getScope() != null) {
            if (xmlParser.requiresConversion("scope", model.getScope())) {
                result.setScope(xmlParser.getTo("scope", model.getScope()));
            } else {
                result.setScope(model.getScope());
            }
        }

        if (xmlParser.requiresConversion("name", model.getMethodName())) {
            result.setMethodName(xmlParser.getTo("name", model.getMethodName()));
        } else {
            result.setMethodName(model.getMethodName());
        }
        stopWatch.stop();
        result.setConversionTime(stopWatch.getResultInSeconds());

        return result;
    }

    /**
     * This method converts a {@link ForStatement} to {@link CSForEachStatement}
     *
     * @param model The <code>ForStatement</code> to be converted
     * @return Converted <code>CSForStatement</code>
     */
    public CSForStatement convert(final ForStatement model) {
        logger.info("ForStatement Conversion");
        CSForStatement result = new CSForStatement();
        if (model.getStatements() != null) {
            for (final Statement internalStatement : model.getStatements()) {
                result.addStatement(convert(internalStatement));
            }
        }
        final StopWatch stopWatch = new StopWatch("ForStatementConversionTimer");
        result.setLoop(model.getLoop());
        stopWatch.stop();
        result.setConversionTime(stopWatch.getResultInSeconds());
        return result;
    }

    /**
     * This method converts a {@link BlockStatement} to {@link CSBlockStatement}
     *
     * @param model The <code>BlockStatement</code> to be converted
     * @return Converted <code>CSBlockStatement</code>
     */
    public CSBlockStatement convert(final BlockStatement model) {
        logger.info("BlockStatement Conversion");
        CSBlockStatement result = new CSBlockStatement();
        if (model.getStatements() != null) {
            for (final Statement internalStatement : model.getStatements()) {
                result.addStatement(convert(internalStatement));
            }
        }
        final StopWatch stopWatch = new StopWatch("BlockStatementConversionTimer");
        result.setBlockStart(model.getBlockStart());
        stopWatch.stop();
        result.setConversionTime(stopWatch.getResultInSeconds());
        return result;
    }
}